package ciprian.google.transport;

import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.util.Log;

import ciprian.google.Constants;
import ciprian.google.HikingAssistantApp;
import ciprian.google.location_mgr.MapPoint;
import ciprian.google.provider.TransportContentProvider;
import ciprian.google.provider.TransportContentProvider.MySQLiteDBHelper;
import ciprian.google.utils.Prefs;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;

import org.json.JSONObject;

import java.io.UnsupportedEncodingException;
import java.net.UnknownHostException;

import java.util.ArrayList;

/**
 * @author Ciprian Ursu
 * 
 *         Provides utility methods for communicating with the server.
 */
public class NetworkUtilities {
    public static final int RESULT_OK = 0x00000001;
    public static final int RESULT_NOK = 0x00000002;
    public static final int RESULT_NETWORK_PROBLEM = 0x00000004;
    public static final int RESULT_UNKNOWN_PROBLEM = 0x00000008;
    public static final int RESULT_SERVER_PROBLEM = 0x00000010;
    public static final int RESULT_AUTHENTICATION_PROBLEM = 0x00000020;

    private static final String TAG = "NetworkUtilities";

    private static final int REGISTRATION_TIMEOUT = 30 * 1000; // ms

    // public static final String BASE_URL = "http://cursu.1025.ro";// server
    // private static final String BASE_URL = "http://10.0.2.2:8888";// local
    public static final String BASE_URL = "http://hikingassistant.info";
    private static final String AUTH_URI = BASE_URL + "/auth.php";
    private static final String DATA_URI = BASE_URL + "/data.php";

    private static final String JSON_RESULT = "result";
    private static final String JSON_USER_ID = "user_id";
    private static final String JSON_TOKEN = "token";

    private static HttpClient mHttpClient;

    private static TransportContentObserver observer;
    private static Handler observerHandler = new Handler();

    public static class AsyncTaskParamType {
        public static final int TYPE_AUTHENTICATION = 0;
        public static final int TYPE_SEND_DATA = 1;

        public static final int SOURCE_AUTHENTICATION_ACTIVITY = 0;
        public static final int SOURCE_UNKNOWN = -1;
        // when there's unknown means that it's not relevant

        public Context context;
        public int result;
    }

    public static class MyHttpResponse {
        public String htmlResponse;
        public int resultStatus;
    }

    protected static MyHttpResponse performHttpRequest(String url,
            final ArrayList<NameValuePair> params) {
        MyHttpResponse ret = new MyHttpResponse();
        final HttpResponse response;
        // TODO: add http headers
        // final Header[] headers = getHttpHeaders(context);

        HttpEntity entity = null;
        try {
            entity = new UrlEncodedFormEntity(params);
        } catch (final UnsupportedEncodingException e) {
            // this should never happen.
            throw new AssertionError(e);
        }

        final HttpPost post = new HttpPost(url);
        post.addHeader(entity.getContentType());
        post.setEntity(entity);

        // post.setHeaders(headers);
        maybeCreateHttpClient();

        try {
            response = mHttpClient.execute(post);
            if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                // new successful request ==> return the response
                ret.htmlResponse = EntityUtils.toString(response.getEntity());
                ret.resultStatus = RESULT_OK;
            } else if (response.getStatusLine().getStatusCode() == HttpStatus.SC_NOT_FOUND) {
                Log.d(TAG, "HTTP error NOT_FOUND at this URL: " + post.getURI());
                ret.htmlResponse = null;
                ret.resultStatus = RESULT_NOK;
            }
        } catch (final UnknownHostException ex) {
            Log.d(TAG, ex.toString());
            ret.htmlResponse = null;
            ret.resultStatus = RESULT_NOK | RESULT_NETWORK_PROBLEM;
        } catch (final HttpHostConnectException hostEx) {
            Log.d(TAG, hostEx.toString());
            ret.htmlResponse = null;
            ret.resultStatus = RESULT_NOK | RESULT_SERVER_PROBLEM;
        } catch (final Exception e) {
            Log.d(TAG, e.toString());
            ret.htmlResponse = null;
            ret.resultStatus = RESULT_NOK | RESULT_UNKNOWN_PROBLEM;
        }

        return ret;
    }

    /**
     * Registers an Observer to be notified when there's a change in the
     * Transport Content Provider.
     */
    public static void registerTransportContentObserver() {
        if (observer == null) {
            // not yet registered
            observer = new TransportContentObserver(observerHandler);
            Uri cpUri = TransportContentProvider.CONTENT_URI;

            // register
            HikingAssistantApp.getMyApplicationContext().getContentResolver()
                    .registerContentObserver(cpUri, false, observer);
        }
    }

    public static void unregisterTransportContentObserver() {
        if (observer != null) {
            // unregister
            HikingAssistantApp.getMyApplicationContext().getContentResolver()
                    .unregisterContentObserver(observer);
        }
    }

    /**
     * There are new points in the provider. Query & send them to server.
     */
    public static void newPointsToSend() {
        String[] projection = { MySQLiteDBHelper.COLUMN_ID, MySQLiteDBHelper.COLUMN_LAT,
                MySQLiteDBHelper.COLUMN_LON, MySQLiteDBHelper.COLUMN_ALT,
                MySQLiteDBHelper.COLUMN_TIME, MySQLiteDBHelper.COLUMN_NAME,
                MySQLiteDBHelper.COLUMN_TYPE, MySQLiteDBHelper.COLUMN_ROUTE_FILE_NAME };
        ContentResolver cr = HikingAssistantApp.getMyApplicationContext().getContentResolver();
        Uri cpUri = Uri.withAppendedPath(TransportContentProvider.CONTENT_URI,
                Uri.encode(MySQLiteDBHelper.SQL_MAIN_TABLE_NAME));
        Cursor cur = cr.query(cpUri, projection, null, null, null);

        if (cur.moveToFirst()) {
            do {
                String name = cur.getString(cur.getColumnIndex(MySQLiteDBHelper.COLUMN_NAME));
                double lat = cur.getDouble(cur.getColumnIndex(MySQLiteDBHelper.COLUMN_LAT));
                double lon = cur.getDouble(cur.getColumnIndex(MySQLiteDBHelper.COLUMN_LON));
                double alt = cur.getDouble(cur.getColumnIndex(MySQLiteDBHelper.COLUMN_ALT));
                long time = cur.getLong(cur.getColumnIndex(MySQLiteDBHelper.COLUMN_TIME));
                int type = cur.getInt(cur.getColumnIndex(MySQLiteDBHelper.COLUMN_TYPE));
                String routeName = cur.getString(cur
                        .getColumnIndex(MySQLiteDBHelper.COLUMN_ROUTE_FILE_NAME));

                MapPoint point = new MapPoint(type, lat, lon, alt, time, name);

                if (sendMapPoint(point, routeName)) {
                    // the point was processed ==> delete it.
                    String whereClause = MySQLiteDBHelper.COLUMN_ID + " = "
                            + cur.getInt(cur.getColumnIndex(MySQLiteDBHelper.COLUMN_ID));
                    cr.delete(cpUri, whereClause, null);
                }
            } while (cur.moveToNext());
        }

        cur.close();
    }

    /**
     * Method used to send the points to server.
     * 
     * @param point
     *            the point information to send.
     */
    public static boolean sendMapPoint(MapPoint point, String routeName) {
        final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
        SharedPreferences settings = Prefs.get();
        String userID = null;
        if ((userID = settings.getString(Constants.PARAM_USER_ID, null)) == null)
            return false;

        String token = null;
        if ((token = settings.getString(Constants.PARAM_TOKEN, null)) == null)
            return false;

        params.add(new BasicNameValuePair(JSON_USER_ID, userID));
        params.add(new BasicNameValuePair(JSON_TOKEN, token));
        params.add(new BasicNameValuePair(Constants.PARAM_POI_TYPE, String.valueOf(point.getType())));
        params.add(new BasicNameValuePair(Constants.PARAM_LATITUDE, String.valueOf(point
                .getLatitude())));
        params.add(new BasicNameValuePair(Constants.PARAM_LONGITUDE, String.valueOf(point
                .getLongitude())));
        params.add(new BasicNameValuePair(Constants.PARAM_ALTITUDE, String.valueOf(point
                .getAltitude())));
        params.add(new BasicNameValuePair(Constants.PARAM_POI_NAME, String.valueOf(point
                .getPointName())));
        params.add(new BasicNameValuePair(Constants.PARAM_TIME, String.valueOf(point.getTime())));
        params.add(new BasicNameValuePair(Constants.PARAM_ROUTE_FILE_NAME, routeName));

        MyHttpResponse response = performHttpRequest(DATA_URI, params);

        if (response.resultStatus == NetworkUtilities.RESULT_OK) {
            // TODO: maybe it would be a good idea to check the html response
            // here too.
            return true;
        } else {
            return false;
        }
    }

    /**
     * Configures the httpClient to connect to the URL provided.
     */
    public static void maybeCreateHttpClient() {
        if (mHttpClient == null) {
            mHttpClient = new DefaultHttpClient();
            final HttpParams params = mHttpClient.getParams();
            HttpConnectionParams.setConnectionTimeout(params, REGISTRATION_TIMEOUT);
            HttpConnectionParams.setSoTimeout(params, REGISTRATION_TIMEOUT);
            ConnManagerParams.setTimeout(params, REGISTRATION_TIMEOUT);
        }
    }

    /**
     * Executes the network requests on a separate thread.
     * 
     * @param runnable
     *            The runnable instance containing network mOperations to be
     *            executed.
     */
    public static TransportAsyncTask performOnBackgroundThread(Context context) {
        TransportAsyncTask at = new TransportAsyncTask();
        at.execute(context);

        return at;
    }

    /**
     * Connects to the server, authenticates the provided username and password.
     */
    public static int authenticate() {
        final MyHttpResponse response;

        SharedPreferences settings = Prefs.get();
        String username = settings.getString(Constants.PARAM_USERNAME, null);
        String password = settings.getString(Constants.PARAM_PASSWORD, null);

        // if the username or password weren't set ==> fail directly
        if (username == null || password == null)
            return NetworkUtilities.RESULT_AUTHENTICATION_PROBLEM | NetworkUtilities.RESULT_NOK;

        ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair(Constants.PARAM_USERNAME, username));
        params.add(new BasicNameValuePair(Constants.PARAM_PASSWORD, password));

        try {
            response = performHttpRequest(AUTH_URI, params);
            if (response.resultStatus == RESULT_OK) {
                JSONObject jsonResult = new JSONObject(response.htmlResponse);
                if (jsonResult.has(JSON_RESULT) && jsonResult.getString(JSON_RESULT).contains("OK")) {
                    SharedPreferences.Editor editor = settings.edit();
                    editor.putString(Constants.PARAM_TOKEN, jsonResult.getString(JSON_TOKEN));
                    editor.putString(Constants.PARAM_USER_ID, jsonResult.getString(JSON_USER_ID));

                    editor.commit();
                } else {
                    // authentication issue
                    return RESULT_NOK | RESULT_AUTHENTICATION_PROBLEM;
                }
            }

            return response.resultStatus;
        } catch (final Exception e) {
            return RESULT_NOK | RESULT_UNKNOWN_PROBLEM;
        }
    }

    /**
     * Attempts to authenticate the user credentials on the server.
     * 
     * @param context
     *            The caller Activity's context
     * 
     * @return Thread The thread on which the network mOperations are executed.
     */
    public static TransportAsyncTask attemptAuth(Context context) {
        return performOnBackgroundThread(context);
    }
}
